﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Effects.GUI
{
    /// <summary>
    ///  One node of the <see cref="LinkedList&lt;T&gt;"/>
    /// </summary>
    /// <typeparam name="T"> Generic type parameter. </typeparam>
    [Serializable]
    public class LinkedListNode<T>
    {
        /// <summary>
        ///  List this node is part of.
        /// </summary>
        private LinkedList<T> list;

        /// <summary>
        ///  Gets the list that stores the node.
        /// </summary>
        public LinkedList<T> List
        {
            get { return list; }
        }
        
        /// <summary>
        ///  Value of the 
        /// </summary>
        private T value;

        /// <summary>
        ///  Gets or sets the value stored by the node.
        /// </summary>
        public T Value
        {
            get { return value; }
            set { this.value = value; }
        }

        /// <summary>
        ///  Next node in the list.
        /// </summary>
        private LinkedListNode<T> next;

        /// <summary>
        ///  Gets or sets the pointer to the next node in the list.
        /// </summary>
        public LinkedListNode<T> Next
        {
            get { return next; }
            set { next = value; }
        }

        /// <summary>
        ///  Previous node in the list.
        /// </summary>
        private LinkedListNode<T> previous;

        /// <summary>
        ///  Gets or sets the pointer to the previous node in the list.
        /// </summary>
        public LinkedListNode<T> Previous
        {
            get { return previous; }
            set { previous = value; }
        }

        /// <summary>
        ///  Creates new node that is part of given linked list, stores given value and is preceded and followed by given nodes.
        /// </summary>
        /// <param name="value"> Value of the node. </param>
        /// <param name="next"> Next node in the list. </param>
        /// <param name="previous"> Previous node in the list. </param>
        /// <param name="list"> List that owns the node </param>
        public LinkedListNode(T value, LinkedListNode<T> next, LinkedListNode<T> previous, LinkedList<T> list)
        {
            this.value = value;
            this.next = next;
            this.previous = previous;
            this.list = list;
        }
    }

    /// <summary>
    ///  Modified linked list.
    /// </summary>
    /// <typeparam name="T"> Generic type parameter. </typeparam>
    /// <remarks>
    ///  <para>
    ///   This class provides simple generic linked list. There is only one reason for self-implementation of this class.
    ///   Statndar .NET linked list doesn't provide functionality for remove all nodes after specified node.
    ///  </para>
    /// </remarks>
    [Serializable]
    public class LinkedList<T>
    {
        #region first_last_node
        // fields and properties for firts and last node of the list

        /// <summary>
        ///  First node of the list.
        /// </summary>
        private LinkedListNode<T> first;

        /// <summary>
        ///  Gets the first node of the list.
        /// </summary>
        public LinkedListNode<T> First
        {
            get { return first; }
        }

        /// <summary>
        ///  Last node of the list.
        /// </summary>
        private LinkedListNode<T> last;

        /// <summary>
        ///  Gets the last node of the list.
        /// </summary>
        public LinkedListNode<T> Last
        {
            get { return last; }
        }

        #endregion.

        #region empty
        // stuff for emptiness syndrome

        /// <summary>
        ///  Indicates whether list is empty (iow stores no values yet)
        /// </summary>
        private bool empty=true;

        /// <summary>
        ///  Gets the empty status of the list.
        /// </summary>
        public bool Empty
        {
            get { return empty;}
        }

        #endregion
         
        #region enumerator
        // enumerator method

        /// <summary>
        ///  Gets IEnumarebla object that runs thorugh all stored values.
        /// </summary>
        /// <returns> IEnumerable object that runs through all stored values. </returns>
        public IEnumerable<T> GetEnumerator()
        {
            LinkedListNode<T> i = first;
            while (i != null)
            {
                yield return i.Value;
                i = i.Next;
            }

            yield break;
        }

        #endregion

        #region add
        // adding new nodes stuff

        /// <summary>
        ///  Adds given value at the end of the list.
        /// </summary>
        /// <param name="value"> Value to add. </param>
        public void AddLast(T value)
        {
            if (first != null)
            {
                // list not empty
                LinkedListNode<T> newLast = new LinkedListNode<T>(value, null, last, this);
                last.Next = newLast;
                last = newLast;
                return;
            }

            first = new LinkedListNode<T>(value, null, null, this);
            last = first;
            empty = false;
        }

        /// <summary>
        ///  Adds value to the list one place in front of given node.
        /// </summary>
        /// <param name="where"> Node to be following new node. </param>
        /// <param name="value"> New value to add. </param>
        public void AddBefore(LinkedListNode<T> where, T value)
        {
            if(where.List != this)
                throw new ArgumentException("Given LinkedListNode isn't part of this list");

            LinkedListNode<T> newNode = new LinkedListNode<T>(value,where,where.Previous,this);
            if (where == first)
                first = newNode;
            else
                where.Previous.Next = newNode;
            where.Previous = newNode;
        }

        #endregion

        #region remove
        // removing nodes from list.

        /// <summary>
        ///  Removes specified node from list.
        /// </summary>
        /// <param name="node"> Node to remove </param>
        public void Remove(LinkedListNode<T> node)
        {
            if(node.List != this)
                throw new ArgumentException("Given LinkedListNode isn't part of this list");

            if (node == first)
            {
                if (node.Next == null)
                {
                    // last node in the list
                    first = null;
                    last = null;
                    empty = true;
                    return;
                }

                node.Next.Previous = null;
                first = node.Next;
                return;
            }

            if (node == last)
            {
                if (node.Previous == null)
                {
                    // last node in the list
                    first = null;
                    last = null;
                    empty = true;
                    return;
                }

                node.Previous.Next = null;
                last = node.Previous;
                return;
            }

            // node is not first and not even last
            LinkedListNode<T> previous = node.Previous;
            previous.Next = node.Next;
            node.Next.Previous = previous;

            node.Next = null;
            node.Previous = null;
        }

        /// <summary>
        ///  Removes all nodes after sepcified node.
        /// </summary>
        /// <param name="node"> Last node to keep in the list. </param>
        public void RemoveAfter(LinkedListNode<T> node)
        {
            if (node.List != this)
                throw new ArgumentException("Given LinkedListNode isn't part of this list");
            
            last = node;
            node.Next.Previous = null;
            node.Next = null;
        }

        /// <summary>
        ///  Removes all nodes before specified node.
        /// </summary>
        /// <param name="node"> First node to keep in the list. </param>
        public void RemoveBefore(LinkedListNode<T> node)
        {
            if (node.List != this)
                throw new ArgumentException("Given LinkedListNode isn't part of this list");
            
            this.first = node;
            node.Previous.Next = null;
            node.Previous = null;
        }

        /// <summary>
        ///  Removes all nodes after specified node and also specified node.
        /// </summary>
        /// <param name="node"> First node to remove. </param>
        public void RemoveFrom(LinkedListNode<T> node)
        {
            if (node.List != this)
                throw new ArgumentException("Given LinkedListNode isn't part of this list");

            if (node == first)
            {
                first = null;
                last = null;
                empty = true;
                return;
            }

            node.Previous.Next = null;
            last = node.Previous;
            node.Previous = null;
        }

        #endregion
    }
}
